/*
* Conditions Of Use
*
* This software was developed by employees of the National Institute of
* Standards and Technology (NIST), an agency of the Federal Government.
* Pursuant to title 15 Untied States Code Section 105, works of NIST
* employees are not subject to copyright protection in the United States
* and are considered to be in the public domain.  As a result, a formal
* license is not needed to use the software.
*
* This software is provided by NIST as a service and is expressly
* provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
* AND DATA ACCURACY.  NIST does not warrant or make any representations
* regarding the use of the software or the results thereof, including but
* not limited to the correctness, accuracy, reliability or usefulness of
* the software.
*
* Permission to use this software is contingent upon your acceptance
* of the terms of this agreement
*
* .
*
*/
/*
 *
 * IPv6 Support added by Emil Ivov (emil_ivov@yahoo.com)<br/>
 * Network Research Team (http://www-r2.u-strasbg.fr))<br/>
 * Louis Pasteur University - Strasbourg - France<br/>
 *
 *Bug fixes for corner cases were contributed by Thomas Froment.
 */
package gov.nist.core;

import gov.nist.javax.sdp.parser.Lexer;

import java.text.ParseException;

/**
 * Parser for host names.
 *
 *@version 1.2
 *
 *@author M. Ranganathan
 */

public class HostNameParser extends ParserCore {

	/**
	 * Determines whether or not we should tolerate and strip address scope
	 * zones from IPv6 addresses. Address scope zones are sometimes returned
	 * at the end of IPv6 addresses generated by InetAddress.getHostAddress().
	 * They are however not part of the SIP semantics so basically this method
	 * determines whether or not the parser should be stripping them (as
	 * opposed simply being blunt and throwing an exception).
	 */
	private boolean stripAddressScopeZones = false;

	public HostNameParser(String hname) {
		this.lexer = new LexerCore("charLexer", hname);

		stripAddressScopeZones
			= Boolean.getBoolean("gov.nist.core.STRIP_ADDR_SCOPES");
	}

	/**
	 * The lexer is initialized with the buffer.
	 */
	public HostNameParser(LexerCore lexer) {
		this.lexer = lexer;
		lexer.selectLexer("charLexer");

		stripAddressScopeZones
			= Boolean.getBoolean("gov.nist.core.STRIP_ADDR_SCOPES");
	}

	private static final char[] VALID_DOMAIN_LABEL_CHAR =
		new char[] {LexerCore.ALPHADIGIT_VALID_CHARS, '-', '.'};
	protected void consumeDomainLabel() throws ParseException {
		if (debug)
			dbg_enter("domainLabel");
		try {
			lexer.consumeValidChars(VALID_DOMAIN_LABEL_CHAR);
		} finally {
			if (debug)
				dbg_leave("domainLabel");
		}
	}

	protected String ipv6Reference() throws ParseException {
		StringBuffer retval = new StringBuffer();
		if (debug)
			dbg_enter("ipv6Reference");

		try {

			if(stripAddressScopeZones){
				while (lexer.hasMoreChars()) {
					char la = lexer.lookAhead(0);
					//'%' is ipv6 address scope zone. see detail at
					//java.sun.com/j2se/1.5.0/docs/api/java/net/Inet6Address.html
					if (LexerCore.isHexDigit(la) || la == '.' || la == ':'
							|| la == '[' ) {
						lexer.consume(1);
						retval.append(la);
					} else if (la == ']') {
						lexer.consume(1);
						retval.append(la);
						return retval.toString();
					} else if (la == '%'){
						//we need to strip the address scope zone.
						lexer.consume(1);

						String rest = lexer.getRest();

						if(rest == null || rest.length() == 0){
							//head for the parse exception
							break;
						}

						//we strip everything until either the end of the string
						//or a closing square bracket (])
						int stripLen = rest.indexOf(']');

						if (stripLen == -1){
							//no square bracket -> not a valid ipv6 reference
							break;
						}

						lexer.consume(stripLen+1);
						retval.append("]");
						return retval.toString();

					} else
						break;
				}
			}
			else
			{
				while (lexer.hasMoreChars())
				{
					char la = lexer.lookAhead(0);
					if (LexerCore.isHexDigit(la) || la == '.'
							|| la == ':' || la == '[') {
						lexer.consume(1);
						retval.append(la);
					} else if (la == ']') {
						lexer.consume(1);
						retval.append(la);
						return retval.toString();
					} else
					break;
				}
			}

			throw new ParseException(
				lexer.getBuffer() + ": Illegal Host name ",
				lexer.getPtr());
		} finally {
			if (debug)
				dbg_leave("ipv6Reference");
		}
	}

	public Host host() throws ParseException {
		if (debug)
			dbg_enter("host");
		try {
			String hostname;

			//IPv6 referene
			if (lexer.lookAhead(0) == '[') {
				hostname = ipv6Reference();
			}
			//IPv6 address (i.e. missing square brackets)
			else if( isIPv6Address(lexer.getRest()) )
			{
				int startPtr = lexer.getPtr();
				lexer.consumeValidChars(
						new char[] {LexerCore.ALPHADIGIT_VALID_CHARS, ':'});
				hostname
					= 	new StringBuffer("[").append(
						lexer.getBuffer().substring(startPtr, lexer.getPtr()))
						.append("]").toString();
			}
			//IPv4 address or hostname
			else {
				int startPtr = lexer.getPtr();
				consumeDomainLabel();
				hostname = lexer.getBuffer().substring(startPtr, lexer.getPtr());
			}

			if (hostname.length() == 0)
				throw new ParseException(
					lexer.getBuffer() + ": Missing host name",
					lexer.getPtr());
			else
				return new Host(hostname);
		} finally {
			if (debug)
				dbg_leave("host");
		}
	}

	/**
	 * Tries to determine whether the address in <tt>uriHeader</tt> could be
	 * an IPv6 address by counting the number of colons that appear in it.
	 *
	 * @param uriHeader the string (supposedly the value of a URI header) that
	 * we have received for parsing.
	 *
	 * @return true if the host part of <tt>uriHeader</tt> could be an IPv6
	 * address (i.e. contains at least two colons) and false otherwise.
	 */
	private boolean isIPv6Address(String uriHeader)
	{
		// approximately detect the end the host part.
		//first check if we have an uri param
		int hostEnd = uriHeader.indexOf(Lexer.QUESTION);

		//if not or if it appears after a semi-colon then the end of the
		//address would be a header param.
		int semiColonIndex = uriHeader.indexOf(Lexer.SEMICOLON);
		if ( hostEnd == -1
			|| (semiColonIndex!= -1 && hostEnd > semiColonIndex) )
			hostEnd = semiColonIndex;

		//if there was no header param either the address
		//continues until the end of the string
		if ( hostEnd == -1 )
			hostEnd = uriHeader.length();

		//extract the address
		String host = uriHeader.substring(0, hostEnd);

		int firstColonIndex = host.indexOf(Lexer.COLON);

		if(firstColonIndex == -1)
			return false;

		int secondColonIndex = host.indexOf(Lexer.COLON, firstColonIndex + 1);

		if(secondColonIndex == -1)
			return false;

		return true;
	}
	/**
	 * Parses a host:port string
	 *
	 * @param allowWS - whether whitespace is allowed around ':', only true for Via headers
	 * @return
	 * @throws ParseException
	 */
	public HostPort hostPort( boolean allowWS ) throws ParseException {
		if (debug)
			dbg_enter("hostPort");
		try {
			Host host = this.host();
			HostPort hp = new HostPort();
			hp.setHost(host);
			// Has a port?
			if (allowWS) lexer.SPorHT(); // white space before ":port" should be accepted
			if (lexer.hasMoreChars()) {
				char la = lexer.lookAhead(0);
				switch (la)
				{
				case ':':
					lexer.consume(1);
					if (allowWS) lexer.SPorHT(); // white space before port number should be accepted
					try {
						String port = lexer.number();
						hp.setPort(Integer.parseInt(port));
					} catch (NumberFormatException nfe) {
						throw new ParseException(
							lexer.getBuffer() + " :Error parsing port ",
							lexer.getPtr());
					}
					break;

				case ';':	// OK, can appear in URIs (parameters)
				case '?':	// same, header parameters
				case '>':	// OK, can appear in headers
				case ' ':	// OK, allow whitespace
				case '\t':
				case '\r':
				case '\n':
				case '/':	// e.g. http://[::1]/xyz.html
					break;
				case '%':
					if(stripAddressScopeZones){
						break;//OK,allow IPv6 address scope zone
					}
				default:
					if (!allowWS) {
						throw new ParseException( lexer.getBuffer() +
								" Illegal character in hostname:" + lexer.lookAhead(0),
								lexer.getPtr() );
					}
				}
			}
			return hp;
		} finally {
			if (debug)
				dbg_leave("hostPort");
		}
	}

	public static void main(String args[]) throws ParseException {
		String hostNames[] =
			{
				"foo.bar.com:1234",
				"proxima.chaplin.bt.co.uk",
				"129.6.55.181:2345",
				":1234",
				"foo.bar.com:         1234",
				"foo.bar.com     :      1234   ",
				"MIK_S:1234"
			};

		for (int i = 0; i < hostNames.length; i++) {
			try {
				HostNameParser hnp = new HostNameParser(hostNames[i]);
				HostPort hp = hnp.hostPort(true);
				System.out.println("["+hp.encode()+"]");
			} catch (ParseException ex) {
				System.out.println("exception text = " + ex.getMessage());
			}
		}

	}
}
